﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Diagnostics;

namespace TsChanManLibrary
{
    public enum SocketPacketType
    {
        ChannelLaunch = 1,
        ChannelStopMessage = 2,
        GetAllChannelsFromServer = 3
    }

    public class SocketPacket
    {
        public SocketPacket(Socket s)
        { Soc = s; }

        public readonly Socket Soc;
        public byte[] dataBuffer = new byte[100000]; // 100 KB for one channel (including picture)
    }

    public class cSockets
    {
        private static Socket socketClient;
        private static Socket socketServer;

        private static AsyncCallback asyServerEndReceive = new AsyncCallback(AfterServerDataReceived);
        private static AsyncCallback asyClientEndReceive = new AsyncCallback(AfterClientDataReceived);

        public static bool IsServerStarted
        { get { return isServerStarted; } }

        private static bool isServerStarted;

        public delegate void ServerDataEventHandler(string sData);
        public static event ServerDataEventHandler OnDataReceived;

        /// <summary>
        /// As the client sends data packet by packet, each packet gets stored
        /// in 'ServerReceiveData' step by step - 'iServerRecvd' remebers how many
        /// bytes have been stored on the server
        /// </summary>
        private static byte[] ServerReceiveData = new byte[100000];
        private static int iServerRecvd = 0;

        /// <summary>
        /// Stores the data the client receives (eg. All channels from the server)
        /// </summary>
        private static List<byte> ClientReceiveData;
        private static SocketPacketType ClientSockPckType;

        #region "Server"

        /// <summary>
        /// This computer starts a server (listening to clients that connect on the 
        /// port configured via cSettings.Cfg.iServerPort) asynchronisly
        /// </summary>
        public static void StartServer()
        {
            // Create the ServerSocket
            socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, cSettings.Cfg.iServerPort);

            socketServer.Bind(ipLocal);
            socketServer.Listen(1); // Just allow one connection as clients won't stay connected for too long

            // Call "OnClientConnect" asynchronisly after Client connects to this server
            socketServer.BeginAccept(new AsyncCallback(OnClientConnect), null);

            isServerStarted = true;
        }

        /// <summary>
        /// Stops listening to the port specified in cSettings.Cfg.iServerPort
        /// </summary>
        public static void StopServer()
        {
            if (socketServer != null)
            { socketServer.Close(); }
            isServerStarted = false;
        }

        /// <summary>
        /// Gets called after client established a connection to this computer
        /// </summary>
        /// <param name="asyn"></param>
        private static void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                Socket socClient = socketServer.EndAccept(asyn);

                SocketPacket sP = new SocketPacket(socClient);

                // asyServerEndReceive = Asynch Callback to AfterServerDataReceived()
                socClient.BeginReceive(sP.dataBuffer, 0,
                                           sP.dataBuffer.Length,
                                           SocketFlags.None,
                                           asyServerEndReceive,
                                           sP);

                // Since the main Socket is now free, it can go back and wait for
                // other clients who are attempting to connect
                socketServer.BeginAccept(new AsyncCallback(OnClientConnect), null);
            }
            catch (ObjectDisposedException)
            { } // No need to handle anything as Exception occures if the connection is closeds 
        }

        /// <summary>
        /// Get's called after a connected clients started sending data to this server
        /// </summary>
        /// <param name="iARes"></param>
        private static void AfterServerDataReceived(IAsyncResult iARes)
        {
            SocketPacket socketData = (SocketPacket)iARes.AsyncState;

            // Complete the BeginReceive() asynchronous call by EndReceive() method
            // which will return the number of bytes written to the stream 
            // by the client
            int iReceived = socketData.Soc.EndReceive(iARes);

            try
            {
                // As we might already have data from the last packet, add the next
                // packet of data to the existing one
                for (int i = 0; i < iReceived; i++)
                {
                    ServerReceiveData[iServerRecvd] = socketData.dataBuffer[i];
                    iServerRecvd++;
                }


                // If we received data, wait for another packet, which might contain
                // more data we need
                if (iReceived != 0)
                {
                    socketData.Soc.BeginReceive(socketData.dataBuffer, 0,
                                                 socketData.dataBuffer.Length,
                                                 SocketFlags.None,
                                                 asyServerEndReceive,
                                                 socketData);
                }
                else // It's over once we receive no more data
                {
                    if (ServerReceiveData[0] != 0)
                    {
                        SocketPacketType SockPckType = (SocketPacketType)Enum.Parse(typeof(SocketPacketType), ServerReceiveData[0].ToString());

                        // Represents the data to be sent back to the client
                        byte[] DataToSend = null;

                        if (SockPckType == SocketPacketType.ChannelLaunch)
                        {
                            ChannelType ChanType = (ChannelType)Enum.Parse(typeof(ChannelType), ServerReceiveData[1].ToString());

                            byte[] ChannelData = new byte[ServerReceiveData.Length - 2];
                            for (int y = 2; y < ServerReceiveData.Length - 2; y++)
                            {
                                ChannelData[y - 2] = ServerReceiveData[y];
                            }

                            ChannelBase c;
                            if (ChanType == ChannelType.DVBSFull)
                            { c = (ChannelDVBSFull)cHelper.DeserializeFromXML(typeof(ChannelDVBSFull), ChannelData); }
                            else if (ChanType == ChannelType.DVBS)
                            { c = (ChannelDVBS)cHelper.DeserializeFromXML(typeof(ChannelDVBS), ChannelData); }
                            else if (ChanType == ChannelType.DVBC)
                            { c = (ChannelDVBC)cHelper.DeserializeFromXML(typeof(ChannelDVBC), ChannelData); }
                            else if (ChanType == ChannelType.DVBT)
                            { c = (ChannelDVBT)cHelper.DeserializeFromXML(typeof(ChannelDVBT), ChannelData); }
                            else
                            { c = (ChannelBase)cHelper.DeserializeFromXML(typeof(ChannelBase), ChannelData); }

                            cChannels cM = new cChannels(ChanType);
                            cM.LaunchChannelFromClientData(c);


                            // Inital wait time TSReader should take to tune into channel,
                            // Parse the transport stream and starts VLC to stream
                            System.Threading.Thread.Sleep(8500);

                            bool vlcFound = false;
                            bool tsrFound = false;
                            int i = 0;
                            while (vlcFound == false)
                            {
                                foreach (Process p in Process.GetProcesses())
                                {
                                    try
                                    {
                                        if (!tsrFound && p.ProcessName == "TSReader")
                                        { tsrFound = true; }
                                        else if (!vlcFound && p.ProcessName == "vlc")
                                        { vlcFound = true; }
                                        else if (vlcFound && tsrFound)
                                        {
                                            DataToSend = cHelper.SerializeToXML("1|OK");
                                            break;
                                        }
                                    }
                                    catch (Exception)
                                    { } // Most likely "Access denied" - just continue with the next Process
                                }

                                // Acts as timeout
                                System.Threading.Thread.Sleep(2000);
                                if (i > 15) break;

                                i++;
                            }
                            if (DataToSend == null)
                            { DataToSend = cHelper.SerializeToXML("0|VLC or TSReader have not been started"); }
                        }
                        else if (SockPckType == SocketPacketType.ChannelStopMessage)
                        {
                            bool IsTSReaderClosed = false;

                            // Look for the TSReader process and close TSReader
                            foreach (Process P in Process.GetProcessesByName("TSReader"))
                            {
                                IsTSReaderClosed = true;
                                P.CloseMainWindow();
                            }

                            // Prepare a message for the client to see if TSReader has bee closed
                            if (IsTSReaderClosed)
                            { DataToSend = cHelper.SerializeToXML("1|TSReader successfully closed"); }
                            else
                            { DataToSend = cHelper.SerializeToXML("0|No TSReader instance found"); }
                        }
                        else if (SockPckType == SocketPacketType.GetAllChannelsFromServer)
                        {
                            // Determite the channel type and send all the channels within this type
                            // to the client
                            ChannelType ChanType = (ChannelType)Enum.Parse(typeof(ChannelType), ServerReceiveData[1].ToString());
                            cChannels c = new cChannels(ChanType);
                            DataToSend = cHelper.SerializeToXML(c.currChannels);
                        }

                        // Insert A SocketType-Flag, so the client knows what to receive
                        byte[] FinalData = new byte[DataToSend.Length + 1];
                        DataToSend.CopyTo(FinalData, 1);
                        FinalData[0] = Convert.ToByte(SockPckType);

                        socketData.Soc.Send(FinalData);
                        socketData.Soc.Close(); // No need to send anymore data

                        // Reset data for the next client
                        iServerRecvd = 0;
                        ServerReceiveData = new byte[100000];
                    }
                    else
                    { socketData.Soc.Shutdown(SocketShutdown.Both); }
                }
            }
            catch (ObjectDisposedException)
            { } // Just continue
            catch (Exception e)
            {
                socketData.Soc.Close();
                MessageBox.Show(e.Message);
            }
        }
        #endregion

        #region "Client"
        /// <summary>
        /// Get's called after the server sent a response saying if the desired channel could be started
        /// </summary>
        /// <param name="iARes"></param>
        private static void AfterClientDataReceived(IAsyncResult iARes)
        {
            SocketPacket theSockId = null;
            int ReceivedBytes = 0;

            try
            {
                theSockId = (SocketPacket)iARes.AsyncState;

                ReceivedBytes = theSockId.Soc.EndReceive(iARes);

                if (ReceivedBytes != 0) // Means there's still data to come...
                {
                    if (ClientReceiveData == null)
                    {
                        ClientReceiveData = new List<byte>();
                        ClientSockPckType = (SocketPacketType)Enum.Parse(typeof(SocketPacketType), theSockId.dataBuffer[0].ToString());

                        // Seperate the SocketTypeFlag from the real data
                        byte[] UsedData = new byte[theSockId.dataBuffer.Length - 1];

                        for (int i = 0; i < ReceivedBytes; i++)
                        {
                            if (i != 0)
                            { ClientReceiveData.Add(theSockId.dataBuffer[i]); }
                        }
                    }
                    else
                    {
                        for (int i = 0; i < ReceivedBytes; i++)
                        { ClientReceiveData.Add(theSockId.dataBuffer[i]); }
                    }

                    // Start listening to the rest of the data
                    theSockId.Soc.BeginReceive(theSockId.dataBuffer, 0,
                                                 theSockId.dataBuffer.Length,
                                                 SocketFlags.None,
                                                 asyClientEndReceive,
                                                 theSockId);
                }
                else
                {
                    byte[] RealData = ClientReceiveData.ToArray();
                    ClientReceiveData = null;

                    if (ClientSockPckType == SocketPacketType.ChannelLaunch || ClientSockPckType == SocketPacketType.ChannelStopMessage)
                    {
                        // Take the byte[] received from the server and convert it to string
                        string text = (string)cHelper.DeserializeFromXML(typeof(string), RealData);

                        if (OnDataReceived != null) { OnDataReceived(text); } // First notify event to have more responsive UI
                    }
                    else if (ClientSockPckType == SocketPacketType.GetAllChannelsFromServer)
                    {
                        List<ChannelBase> Channels = (List<ChannelBase>)cHelper.DeserializeFromXML(typeof(List<ChannelBase>), RealData);
                        
                        int iChCount = 0;
                        int iChNew = 0;
                        cChannels c = new cChannels(cSettings.Cfg.cTvHardware);
                        foreach (ChannelBase cb in Channels)
                        {
                            if (!c.ChannelExists(cb))
                            { c.AddChannel(cb); iChNew++; } // Add the channel to the current list
                            iChCount++;
                        }
                        
                        if (OnDataReceived != null) // Prepare a status message
                        { OnDataReceived(iChNew + " new channels added (" + iChCount + " channels received)"); }
                    }
                }
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
            }
            catch (Exception)
            {
                if (OnDataReceived != null) { OnDataReceived("0|Could not process request received from server"); }
            }

            // After we received the server reponse, no more network communication is needed
            if (theSockId != null && ReceivedBytes == 0)
            {
                try
                {
                    theSockId.Soc.Shutdown(SocketShutdown.Both);
                    theSockId.Soc.Close();
                }
                catch (ObjectDisposedException)
                { System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n"); }
            }
        }

        public static void SendChannelDataToServer(ChannelBase cn)
        { SendDataToServer(SocketPacketType.ChannelLaunch, cn); }

        public static void StopChannelOnServer()
        { SendDataToServer(SocketPacketType.ChannelStopMessage, null); }

        public static void GetAllChannelsFromServer()
        { SendDataToServer(SocketPacketType.GetAllChannelsFromServer, null); }

        private static void SendDataToServer(SocketPacketType sockPckType, ChannelBase cn)
        {
            // The byte[] which will be sent to the server
            // The first byte represents the type of data with the same index as in SocketPacketType:
            //  1 == SocketPacketType.ChannelLaunch
            byte[] DataForServer = new byte[1];

            if (sockPckType == SocketPacketType.ChannelLaunch)
            {
                // Set the VLC command, so TSReader accepts it
                cn.VLCCommand = "<IP> " + cSettings.Cfg.sVlcServerCommand.Replace("{0}", cSettings.Cfg.sServerIP);

                byte[] ChannelInBytes = cHelper.SerializeToXML(cn); // Convert the channel to byte[]

                DataForServer = new byte[ChannelInBytes.Length + 2];
                DataForServer[1] = Convert.ToByte(cSettings.Cfg.cTvHardware);
                ChannelInBytes.CopyTo(DataForServer, 2);
            }
            else if (sockPckType == SocketPacketType.GetAllChannelsFromServer)
            {
                DataForServer = new byte[2];
                DataForServer[1] = Convert.ToByte(cSettings.Cfg.cTvHardware);
            }

            // Set the type of channel according to the SocketPacketType
            DataForServer[0] = Convert.ToByte(sockPckType);

            SocketClientInitalConnect();

            socketClient.Send(DataForServer, DataForServer.Length, 0);

            SocketPacket socP = new SocketPacket(socketClient);

            //Start listening to the data asynchronously
            socketClient.BeginReceive(socP.dataBuffer,
                                                    0, socP.dataBuffer.Length,
                                                    SocketFlags.None,
                                                    asyClientEndReceive,
                                                    socP);

            // As the client won't send data to the server, just wait for a response
            socketClient.Shutdown(SocketShutdown.Send);
        }

        private static void SocketClientInitalConnect()
        {
            IPAddress ipa = IPAddress.Parse(cSettings.Cfg.sServerIP);
            EndPoint ep = new IPEndPoint(ipa, cSettings.Cfg.iServerPort);
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                socketClient.Connect(ep);
            }
            catch (Exception)
            { socketClient.Close(); return; }
        }
        #endregion
    }
}
